package bsh;

import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.DLOAD;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.IFNONNULL;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.POP;
import static org.objectweb.asm.Opcodes.RETURN;

import java.lang.reflect.Modifier;
import java.math.MathContext;
import java.util.Arrays;
import java.util.Map;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import bsh.asm.acessor.ObjectAccessor;
import bsh.exception.CompilationFailedException;
import bsh.meta.ArrayTypeWrapper;
import bsh.meta.JavaClassWrapper;
import bsh.meta.MethodDescriptor;
import bsh.meta.TypeWrapper;
import bsh.meta.UnfinishedMethodDescriptor;

/**
 * This class is responsible for writing the default implementation of the {@link CompiledScript}'s methods when some or all of them are
 * missing. Mostly it delegates to {@link CompiledScriptHelper}'s static methods.
 * 
 * @author takeshi
 */
public class CompiledScriptPrototypeWriter {

    private static final TypeWrapper[] EMPTY_ARRAY = new TypeWrapper[]{};

    private static final String COMPILED_SCRIPT_HELPER = "bsh/CompiledScriptHelper";

    /**
     * Attempts to implement all the missing methods from the {@link CompiledScript} interface
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void write(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        writeFieldMethods(cw, state);
        writeIndyMethods(cw, state);
        writeMathMethods(cw, state);
        writeStackMethods(cw, state);
    }

    /**
     * Attempts to implement the methods that manipulates the stack
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeStackMethods(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        writePopStackMethod(cw, state);
        writePopMethodMethod(cw, state);
        writeSetLocalVariableMethod(cw, state);
        writeNewStackFrameMethod(cw, state);
    }

    /**
     * Attempts to implement the methods regarding BigDecimal and MathContext
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeMathMethods(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        writeGetMathContextMethod(cw, state);
        writeSetMathContextMethod(cw, state);
        writeLocalToBigDecimalMethod(cw, state);
    }

    /**
     * Attempts to implement the dynamic invocation methods
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeIndyMethods(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        writeCreateObjectMethod(cw, state);
        writeInvokeDynamicMethod(cw, state);
    }

    /**
     * Attempts to implement the fields methods
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeFieldMethods(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        writeSetFieldsMethod(cw, state);
        writeResetAllFieldsMethod(cw, state);
        writeResetFieldMethod(cw, state);
        writeSetFieldMethod(cw, state);
        writeGetFieldValueMethod(cw, state);
        writeGetDynamicFieldMethod(cw, state);
        writeSetDynamicFieldMethod(cw, state);
        writeClearDynamicFieldsMethod(cw, state);
    }

    /**
     * Attempts to implement the {@link CompiledScript#newStackFrame(String)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeNewStackFrameMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "newStackFrame";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.STRING_WRAPPER };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));
        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, "newStackFrame", "(Ljava/lang/String;)V", null, null);
        visitor.visitCode();
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Verifies if the method is implemented in this class or any superclass
     * 
     * @param state the {@link CompilationState}
     * @param methodName the name of the method
     * @param argTypes {@link TypeWrapper}
     * @return boolean
     * @throws CompilationFailedException e
     */
    protected boolean isMethodImplemented(CompilationState state, final String methodName, final TypeWrapper[] argTypes)
            throws CompilationFailedException {
        MethodDescriptor methodDescriptor =
                state.getUnit().reflectionUtils().findMethodForTypes(state.getUnit().thisType(), methodName, argTypes);
        if (methodDescriptor == null) {
            return false;
        }
        boolean methodIsAbstract = !Modifier.isAbstract(methodDescriptor.modifiers());
        if (!methodIsAbstract) {
            state.getUnit().thisType().writeMethod(-1, methodName, Arrays.asList(argTypes));
        }
        return methodIsAbstract;
    }

    /**
     * Attempts to implement the {@link CompiledScript#setLocalVariable(String, Object)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeSetLocalVariableMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "setLocalVariable";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.STRING_WRAPPER, JavaClassWrapper.OBJECT_WRAPPER };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));
        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "(Ljava/lang/String;Ljava/lang/Object;)V", null, null);
        visitor.visitCode();
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();

    }

    /**
     * Attempts to implement the {@link CompiledScript#popMethod()} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writePopMethodMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "popMethod";
        final TypeWrapper[] argTypes = new TypeWrapper[]{};
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));
        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "()V", null, null);
        visitor.visitCode();
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#popStack()} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writePopStackMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "popStack";
        final TypeWrapper[] argTypes = new TypeWrapper[]{};
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "()V", null, null);
        visitor.visitCode();
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#localToBigDecimal(double)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeLocalToBigDecimalMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "localToBigDecimal";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.DOUBLE_WRAPPER };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.BIG_DECIMAL_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "(D)Ljava/math/BigDecimal;", null, null);
        visitor.visitCode();
        visitor.visitTypeInsn(NEW, "java/math/BigDecimal");
        visitor.visitInsn(DUP);
        visitor.visitVarInsn(DLOAD, 1);
        visitor.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(D)Ljava/lang/String;");
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitMethodInsn(INVOKEVIRTUAL, state.getUnit().thisType().internalName(), "getMathContext", "()Ljava/math/MathContext;");
        visitor.visitMethodInsn(INVOKESPECIAL, "java/math/BigDecimal", "<init>", "(Ljava/lang/String;Ljava/math/MathContext;)V");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#setMathContext(MathContext)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeSetMathContextMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "setMathContext";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.wrapperFor(MathContext.class) };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit().thisType().addMethod(newUnfinishedMethod(state, methodName, argTypes, JavaClassWrapper.VOID_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "(Ljava/math/MathContext;)V", null, null);
        visitor.visitCode();

        ObjectAccessor acc = state.getUnit().thisType().getMathContextFieldAccessor();
        acc.preStore(visitor, state);
        visitor.visitVarInsn(ALOAD, 1);
        state.pushStack(MathContext.class);
        acc.storeObject(visitor, state);
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the method with the given name, parameters and return type
     * 
     * @param state the {@link CompilationState}
     * @param methodName the method name
     * @param argTypes the declared argument types
     * @param returnType the declared return type
     * @return {@link UnfinishedMethodDescriptor}
     */
    public UnfinishedMethodDescriptor newUnfinishedMethod(CompilationState state, final String methodName, final TypeWrapper[] argTypes,
            TypeWrapper returnType) {
        return new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes, returnType);
    }

    /**
     * Attempts to implement the {@link CompiledScript#getMathContext()} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeGetMathContextMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "getMathContext";
        final TypeWrapper[] argTypes = new TypeWrapper[]{};
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        TypeWrapper returnType = JavaClassWrapper.wrapperFor(MathContext.class);
        state.getUnit().thisType().addMethod(this.newUnfinishedMethod(state, methodName, argTypes, returnType));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "()Ljava/math/MathContext;", null, null);
        visitor.visitCode();
        ObjectAccessor acc = state.getUnit().thisType().getMathContextFieldAccessor();
        acc.loadObject(visitor, state);
        state.popStack();

        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#invokeDynamic(int, Object, String, Object[])} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeInvokeDynamicMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "invokeDynamic";
        final int methodNameIndex = 3;
        final int methodArgumentsIndex = 4;
        final TypeWrapper[] argTypes =
                new TypeWrapper[]{ JavaClassWrapper.INT_WRAPPER, JavaClassWrapper.OBJECT_WRAPPER, JavaClassWrapper.STRING_WRAPPER,
                        new ArrayTypeWrapper(JavaClassWrapper.OBJECT_WRAPPER) };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(new TypeWrapper[]{ JavaClassWrapper.wrapperFor(Throwable.class) }, state.getUnit()
                                .thisType(), methodName, argTypes, JavaClassWrapper.OBJECT_WRAPPER));

        MethodVisitor visitor =
                cw.visitMethod(ACC_PUBLIC, methodName, "(ILjava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;",
                        null, new String[]{ "java/lang/Throwable" });
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 2);
        visitor.visitVarInsn(ILOAD, 1);
        visitor.visitVarInsn(ALOAD, methodNameIndex);
        visitor.visitVarInsn(ALOAD, methodArgumentsIndex);
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitMethodInsn(INVOKESTATIC, COMPILED_SCRIPT_HELPER, "invokeDynamic",
                "(Ljava/lang/Object;ILjava/lang/String;[Ljava/lang/Object;Lbsh/CompiledScript;)Ljava/lang/Object;");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#createObject(int, Class, Object[])} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeCreateObjectMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "createObject";
        final int constructorArgumentsIndex = 3;
        final TypeWrapper[] argTypes =
                new TypeWrapper[]{ JavaClassWrapper.INT_WRAPPER, JavaClassWrapper.wrapperFor(Class.class), JavaClassWrapper.STRING_WRAPPER,
                        new ArrayTypeWrapper(JavaClassWrapper.OBJECT_WRAPPER) };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(new TypeWrapper[]{ JavaClassWrapper.wrapperFor(Throwable.class) }, state.getUnit()
                                .thisType(), methodName, argTypes, JavaClassWrapper.OBJECT_WRAPPER));

        MethodVisitor visitor =
                cw.visitMethod(ACC_PUBLIC, methodName, "(ILjava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object;",
                        "(ILjava/lang/Class<*>;[Ljava/lang/Object;)Ljava/lang/Object;", new String[]{ "java/lang/Throwable" });
        visitor.visitCode();
        visitor.visitVarInsn(ILOAD, 1);
        visitor.visitVarInsn(ALOAD, 2);
        visitor.visitVarInsn(ALOAD, constructorArgumentsIndex);
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitMethodInsn(INVOKESTATIC, COMPILED_SCRIPT_HELPER, "createObject",
                "(ILjava/lang/Class;[Ljava/lang/Object;Lbsh/CompiledScript;)Ljava/lang/Object;");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#clearDynamicFields()} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeClearDynamicFieldsMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "clearDynamicFields";
        final TypeWrapper[] argTypes = new TypeWrapper[]{};
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "()V", null, null);
        visitor.visitCode();

        ObjectAccessor objectAccessor = state.getUnit().thisType().getDynamicVariablesFieldAccessor();
        objectAccessor.loadObject(visitor, state);
        state.popStack();

        visitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "clear", "()V");
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#setDynamicField(String, Object)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeSetDynamicFieldMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "setDynamicField";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.STRING_WRAPPER, JavaClassWrapper.OBJECT_WRAPPER };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "(Ljava/lang/String;Ljava/lang/Object;)V", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 2);
        Label l1 = new Label();
        visitor.visitJumpInsn(IFNONNULL, l1);

        ObjectAccessor objectAccessor = state.getUnit().thisType().getDynamicVariablesFieldAccessor();
        objectAccessor.loadObject(visitor, state);
        state.popStack();

        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "remove", "(Ljava/lang/Object;)Ljava/lang/Object;");
        visitor.visitInsn(POP);
        Label l3 = new Label();
        visitor.visitJumpInsn(GOTO, l3);
        visitor.visitLabel(l1);

        objectAccessor.loadObject(visitor, state);
        state.popStack();

        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitVarInsn(ALOAD, 2);
        visitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        visitor.visitInsn(POP);
        visitor.visitLabel(l3);
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#getDynamicField(String)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeGetDynamicFieldMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "getDynamicField";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.STRING_WRAPPER };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.OBJECT_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "(Ljava/lang/String;)Ljava/lang/Object;", null, null);
        visitor.visitCode();
        ObjectAccessor objectAccessor = state.getUnit().thisType().getDynamicVariablesFieldAccessor();
        objectAccessor.loadObject(visitor, state);
        state.popStack();
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#getFieldValue(String)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeGetFieldValueMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "getFieldValue";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.STRING_WRAPPER };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.OBJECT_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "(Ljava/lang/String;)Ljava/lang/Object;", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitMethodInsn(INVOKESTATIC, COMPILED_SCRIPT_HELPER, "getFieldValue",
                "(Lbsh/CompiledScript;Ljava/lang/String;)Ljava/lang/Object;");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#setField(String, Object)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeSetFieldMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "setField";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.STRING_WRAPPER, JavaClassWrapper.OBJECT_WRAPPER };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "(Ljava/lang/String;Ljava/lang/Object;)V", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitVarInsn(ALOAD, 2);
        visitor.visitMethodInsn(INVOKESTATIC, COMPILED_SCRIPT_HELPER, "setField",
                "(Lbsh/CompiledScript;Ljava/lang/String;Ljava/lang/Object;)V");
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#resetField(String)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeResetFieldMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "resetField";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.STRING_WRAPPER };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, "resetField", "(Ljava/lang/String;)V", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitMethodInsn(INVOKESTATIC, COMPILED_SCRIPT_HELPER, "resetField", "(Lbsh/CompiledScript;Ljava/lang/String;)V");
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#resetAllFields()} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeResetAllFieldsMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "resetAllFields";
        final TypeWrapper[] argTypes = new TypeWrapper[]{};
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));

        MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, methodName, "()V", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitMethodInsn(INVOKESTATIC, COMPILED_SCRIPT_HELPER, "resetAllFields", "(Lbsh/CompiledScript;)V");
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    /**
     * Attempts to implement the {@link CompiledScript#setField(String, Object)} method
     * 
     * @param cw the {@link ClassVisitor} to write the methods to
     * @param state the {@link CompilationState}
     * @throws CompilationFailedException e
     */
    public void writeSetFieldsMethod(ClassVisitor cw, CompilationState state) throws CompilationFailedException {
        final String methodName = "setFields";
        final TypeWrapper[] argTypes = new TypeWrapper[]{ JavaClassWrapper.wrapperFor(Map.class) };
        if (isMethodImplemented(state, methodName, argTypes)) {
            return;
        }
        state.getUnit()
                .thisType()
                .addMethod(
                        new UnfinishedMethodDescriptor(EMPTY_ARRAY, state.getUnit().thisType(), methodName, argTypes,
                                JavaClassWrapper.VOID_WRAPPER));

        MethodVisitor visitor =
                cw.visitMethod(ACC_PUBLIC, "setFields", "(Ljava/util/Map;)V", "(Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)V",
                        null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitMethodInsn(INVOKESTATIC, COMPILED_SCRIPT_HELPER, "setFields", "(Lbsh/CompiledScript;Ljava/util/Map;)V");
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

}
